package com.cmsz.order.controller;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mobile.device.site.SitePreference;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.cmsz.cashier.utils.CommonConstants;
import com.cmsz.order.remote.BetterRemoteService;
import com.cmsz.order.remote.SignResult;
import com.cmsz.framework.ftl.FtlTemplateEngine;
import com.cmsz.framework.http.HttpClient;
import com.cmsz.framework.log.LogHandler;
import com.cmsz.framework.util.JsonUtil;
import com.cmsz.framework.util.KeyValueUtil;
import com.cmsz.framework.util.SerialNumber;
import com.cmsz.framework.util.XmlUtils;
import com.fasterxml.jackson.core.JsonProcessingException;

/**
 * 
 * @author zhoujirong
 *
 */
@Controller
@RequestMapping("/h5")
public class H5Action {

	private static LogHandler logger = LogHandler.getLogger(H5Action.class);
	
	@Autowired
	private FtlTemplateEngine messageTemplateEngine;
	@Resource(name="BankSecurityRemoting")
	private BetterRemoteService betterRemoteService;
	@Autowired
	private HttpClient httpClient;
	@Value("${orderUrl}")
	private String orderUrl;
	@Value("${notifyUrl}")
	private String notifyUrl;
	@Value("${wxPayUrl}")
	private String wxPayUrl;
	@Value("${aliPayUrl}")
	private String aliPayUrl;
	@Value("${unionPayUrl}")
	private String unionPayUrl;
	@Value("${sinmulator}")
	private String sinmulator;
	@Value("${parameterDir}")
	private String parameterDir;
	
	@Value("${h5.weixin.redirect.url}")
	private String redirectUrl;
	@Value("${weixin.auth.url}")
	private String authUrl;
	
	/**
	 * 接收商户提交的预下单请求，转发到统一支付，得到统一支付的应答并向客户端返回支付宝支付表单
	 * @param req
	 * @return
	 * @throws IOException
	 */
	@RequestMapping(value = "/submit.action", produces = "text/html; charset=utf-8", method = { RequestMethod.POST,
			RequestMethod.GET })
	public String submit(ModelMap map, HttpServletRequest req,HttpServletResponse rsp,SitePreference sitePreference) throws IOException {
		Map<String, String> reqMap = comvertMap(req.getParameterMap());
		String userAgent = req.getHeader( "USER-AGENT" ).toLowerCase(); 
		String clientType = getClientType(sitePreference,userAgent);;
		try {
			logger.messageReceive("接收到商户请求："+reqMap.toString());
			//校验参数
			validateData(reqMap,clientType);
			logger.info("H5网站|H5Action|参数校验完成",null);
			//验签
			checkSign(reqMap);
			logger.info("H5网站|H5Action|验签完成",null);
			//根据商户请求数据，补充商户前置所需参数
			fillField(reqMap,clientType);
			
			//转换成xml
			String orderXml = messageTemplateEngine.genMessage("order.xml", reqMap);
			orderXml = orderXml.replaceAll("\t|\r|\n","");
			logger.info("H5网站|H5Action|生成请求报文完成",null);
			//签名
			orderXml = signForFront(orderXml);
			logger.info("H5网站|H5Action|生成请求报文完成",null);
			
			if(CommonConstants.CLIENT_WEIXIN.equals(clientType)){
				String wxAuthUrl = combineAuthUrl(orderXml,reqMap);
				map.addAttribute("authUrl", wxAuthUrl);
				logger.info("H5网站|H5Action|设置微信授权页面参数完成",null);
				logger.messageSend("H5网站|H5Action|设置微信授权页面参数完成:"+orderXml);
				return "page/wxauth";
			}else{
				//发送
				String frontRsp = sendToFront(orderXml);
				logger.info("H5网站|H5Action|发送请求报文完成",null);
				//根据响应验签
				checkSignForFront(frontRsp);
				logger.info("H5网站|H5Action|统一支付响应报文验签完成",null);
				//检查统一支付返回码
				checkCode(frontRsp);
				//保存Parameters字段参数
				saveParameters(frontRsp);
				
				String paymentType = XmlUtils.parseNodeValueFromXml("<PaymentType>", "</PaymentType>", frontRsp);
				map.addAttribute("paymentType", paymentType);
				String parameters = XmlUtils.parseNodeValueFromXml("<Parameters><![CDATA[", "]]></Parameters>", frontRsp);
				map.addAttribute("parameters", parameters);
				logger.messageSend("向商户返回自动跳转报文");
				logger.info("H5网站|H5Action|向商户返回自动跳转报文完成",null);
				// todo page/createForm.html
				return "page/createForm";
			}
		} catch (Exception e) {
			logger.error("H5网站|H5Action|提交订单失败:"+reqMap.toString(), e);
			map.addAttribute("errMsg","提交订单失败");
			return "page/error";
		}
	}

	private String getClientType(SitePreference sitePreference, String userAgent) {
		 if(userAgent.toLowerCase().contains(CommonConstants.KEYWORD_WEIXIN)){
		    	 return CommonConstants.CLIENT_WEIXIN;
		     }
		 if (sitePreference == SitePreference.NORMAL) {
		    	return CommonConstants.CLIENT_PC;
	        } else if (sitePreference == SitePreference.MOBILE) {
	        	return CommonConstants.CLIENT_MOBILE;
	        } else if (sitePreference == SitePreference.TABLET) {
	        	return CommonConstants.CLIENT_TABLET;
	        } else {
	        	return CommonConstants.CLIENT_PC;
	        }
		
	}
	
	private void saveParameters(String frontRsp) {
		if ("open".equals(sinmulator)) {
			try {
				String dir = parameterDir;
				if (StringUtils.isBlank(dir)) {
					File file = new File(H5Action.class.getResource("/").getPath());
					dir = file.getParentFile().getParentFile().getParentFile().getAbsolutePath() + "/payprod-sinmulator/ParamDB/";
				}
				Files.createDirectories(Paths.get(dir));
				String orderNo = XmlUtils.parseNodeValueFromXml("<OrderNo>", "</OrderNo>", frontRsp);
				Path path = Paths.get(dir + "/" + orderNo + ".txt");
				String parameters = XmlUtils.getNodeValueFromXml("<Parameters>", "</Parameters>", frontRsp);
				parameters = parameters.replaceAll("\t|\r|\n","");
				List<String> lines = new ArrayList<>();
				for (String line : parameters.split("&")) {
					lines.add(line + "\n");
				}
				Files.write(path, lines, Charset.forName("UTF-8"));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private String combineAuthUrl(String  orderXml, Map<String, String> reqMap) throws UnsupportedEncodingException, JsonProcessingException {
//		String stateUrlEncode = URLEncoder.encode(orderXml,"utf-8");
		/**
		 * base64 解决微信不重定向回来的问题
		 * orderXmlBase64的URL编码：由于发送到微信时被解码了一次，所以重定向时的链接上的参数仍需要URL编码，
		 * 否则从客户浏览器重定向过来的数据会URL编码的问题，因此重定向时URL不需要两次解码，附带的参数需要
		 */
		String orderXmlBase64 = new String(Base64.getEncoder().encode(orderXml.getBytes("UTF-8")));
		String orderXmlBase64UrlEncode = URLEncoder.encode(orderXmlBase64,"utf-8");
		String dataUrl = redirectUrl+"?orderdata="+orderXmlBase64UrlEncode;
		String redirectUrlEncode = URLEncoder.encode(dataUrl,"utf-8");
		String authforOpenIdUrl = authUrl+"?appid="+reqMap.get("WeiXinAppId")+"&redirect_uri="+redirectUrlEncode+
				"&response_type=code&scope=snsapi_base&state=xmldata#wechat_redirect";
		return authforOpenIdUrl;
	}
	
	private String sendToFront(String orderXml) throws ClientProtocolException, IOException {
		String merchantFrontRsp = httpClient.sendStringMsg(orderUrl, orderXml);
		if (StringUtils.isBlank(merchantFrontRsp)) {
			throw new RuntimeException("网络通讯异常");
		}
		return merchantFrontRsp;
	}
	
	/**
	 * 根据商户请求数据，补充商户前置所需参数
	 * @param reqMap
	 * @param clientType 
	 */
	private void fillField(Map<String, String> reqMap, String clientType) {
		String orderNo = reqMap.get("OrderNo");
		reqMap.put("ReqSys", orderNo.substring(0, 4));
		reqMap.put("ReqDate", orderNo.substring(4, 12));
		reqMap.put("ReqTransID", "0001" + SerialNumber.generateRandomSerial(28));
		reqMap.put("ReqDateTime", orderNo.substring(4, 18));
		if (StringUtils.isBlank(reqMap.get("NotifyURL"))) {
			reqMap.put("NotifyURL", notifyUrl);
		}
		if(CommonConstants.CLIENT_WEIXIN.equals(clientType)){
			reqMap.put("PaymentType", "WEIXIN-JSAPI");
		}
	}

	/**
	 * 检查统一支付返回码
	 * @param xml
	 */
	private void checkCode(String xml) {
		String rspCode = XmlUtils.parseNodeValueFromXml("<RspCode>", "</RspCode>", xml);
		if (!"010A00".equals(rspCode)) {
			throw new RuntimeException("商户前置返回失败：" + rspCode);
		}
		String resultCode = XmlUtils.parseNodeValueFromXml("<ResultCode>", "</ResultCode>", xml);
		if (!"014A17".equals(resultCode)) {
			throw new RuntimeException("商户前置返回失败：" + resultCode);
		}
	}
	
	/**
	 * 组装表单数据，客户端拿到表单自动提交给第三方支付机构
	 * @param xml
	 * @return
	 */
	private String createThirdForm(String xml) {
		String paymentType = XmlUtils.parseNodeValueFromXml("<PaymentType>", "</PaymentType>", xml);
		String parameters = XmlUtils.parseNodeValueFromXml("<Parameters><![CDATA[", "]]></Parameters>", xml);
		StringBuilder sb = new StringBuilder();
		String payUrl = wxPayUrl;
		if (paymentType.startsWith("ALIPAY")) {
			payUrl = aliPayUrl;
		} else if (paymentType.startsWith("UNIONPAY")) {
			payUrl = unionPayUrl;
		}
		sb.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head><body><form id=\"paysubmit\" name=\"paysubmit\" action=\""+payUrl+"\" method=\"post\">");
		for(String s : parameters.split("&")) {
			int index = s.indexOf("=");
			String key = s.substring(0,index);
			String value = s.substring(index+1);
			sb.append("<input type=\"hidden\" name=\""+key+"\" id=\""+key+"\" value=\""+value+"\" />");
		}
		sb.append("</form><script>document.forms['paysubmit'].submit();</script></body></html>");
		return sb.toString();
	}

	/**
	 * 对商户请求数据验签
	 * @param orderXml
	 */
	private void checkSignForFront(String orderXml) {
		StringBuilder plainText = new StringBuilder();
		String cerId = XmlUtils.parseNodeValueFromXml("<CerID>", "</CerID>", orderXml);
		String signature = XmlUtils.parseNodeValueFromXml("<SignValue>", "</SignValue>", orderXml);
		String header = XmlUtils.parseNodeValueFromXml("<Header>", "</Header>", orderXml);
		String body = XmlUtils.parseNodeValueFromXml("<Body>", "</Body>", orderXml);
		plainText.append("<Header>").append(header).append("</Header>|<Body>").append(body).append("</Body>");
		try {
			if (betterRemoteService.verify(cerId, plainText.toString(), signature)) {
				return;
			}
		} catch (Exception e) {
			throw new RuntimeException("系統未知异常");
		}
	}

	/**
	 * 签名给统一支付，商户前置要验签
	 * @param orderXml
	 * @return
	 */
	private String signForFront(String orderXml) {
		StringBuilder plainText = new StringBuilder();
		String header = XmlUtils.parseNodeValueFromXml("<Header>", "</Header>", orderXml);
		String body = XmlUtils.parseNodeValueFromXml("<Body>", "</Body>", orderXml);
		plainText.append("<Header>").append(header).append("</Header>|<Body>").append(body).append("</Body>");
		try {
			String jsonStr = betterRemoteService.sign(null, plainText.toString());
			SignResult signResult = JsonUtil.jsonStr2Obj(jsonStr, SignResult.class);
			orderXml = XmlUtils.relaceNodeContent("<CerID>", "</CerID>", signResult.getCerId(), orderXml);
			orderXml = XmlUtils.relaceNodeContent("<SignValue>", "</SignValue>", signResult.getSignValue(), orderXml);
			return orderXml;
		} catch (Exception e) {
			throw new RuntimeException("系統未知异常");
		}
	}

	/**
	 * 对商户前置的签名进行验签
	 * @param reqMap
	 */
	private void checkSign(Map<String, String> reqMap) {
		String cerId = reqMap.remove("CerID");
		String signature = reqMap.remove("SignValue");
		try {
			String plainText = KeyValueUtil.mapToString(reqMap);
			if (betterRemoteService.verify(cerId, plainText, signature)) {
				return;
			}
		} catch (Exception e) {
			throw new RuntimeException("验签失败");
		}
	}

	/**
	 * 校验商户请求数据
	 * @param reqMap
	 * @param clientType 
	 */
	private void validateData(Map<String, String> reqMap, String clientType) {
		Map<String, String> checkMap = new HashMap<>();
		checkMap.putAll(reqMap);
		String orderNo = checkMap.remove("OrderNo");
		checkOrderNo(orderNo);
		checkField(checkMap, "BuyerID", false, true, 32);
		checkField(checkMap, "IDType", false, false, 2);
		checkField(checkMap, "IDValue", false, true, 32);
		checkField(checkMap, "OrderMoney", true, true, 9);
		checkField(checkMap, "Payment", true, true, 9);
		checkField(checkMap, "Gift", true, true, 9);
		checkField(checkMap, "MerActivityID", false, true, 32);
		checkField(checkMap, "PaymentType", true, true, 32);
		checkField(checkMap, "PaymentLimit", false, true, 32);
		checkField(checkMap, "ProductID", true, true, 32);
		checkField(checkMap, "ProductName", true, true, 32);
		checkField(checkMap, "ProductDesc", true, true, 32);
		checkField(checkMap, "ProductURL", false, true, -1);
		checkField(checkMap, "NotifyURL", true, true, -1);
		checkField(checkMap, "ReturnURL", false, true, -1);
		checkField(checkMap, "DefaultBank", false, true, -1);
		checkField(checkMap, "ClientIP", false, true, 15);
		checkField(checkMap, "CustomParam", false, true, 1024);
		checkField(checkMap, "WeiXinAppId", false, true, 32);
//		checkField(checkMap, "WeiXinOpenId", false, true, 128);
		checkField(checkMap, "CerID", true, true, 32);
		checkField(checkMap, "SignValue", true, true, 2048);
		for (Map.Entry<String, String> entry : checkMap.entrySet()) {
			throw new IllegalArgumentException("非法参数：" + entry.getKey());
		}
		
		boolean bankUnUsable = ! CommonConstants.CLIENT_PC.equals(clientType) && "ALI-BANK".equals(checkMap.get("PaymentType"));
		if(bankUnUsable){
			throw new IllegalArgumentException("非法参数：该客户端类型："+clientType+ "不能使用PaymentType：ALI-BANK");
		}
	}
	/**
	 * 校验订单号
	 * @param orderNo
	 */
	private void checkOrderNo(String orderNo) {
		if (StringUtils.isNotBlank(orderNo) && orderNo.length() >= 18) {
			try {
				new SimpleDateFormat("yyyyMMddHHmmss").parse(orderNo.substring(4, 18));
				return;
			} catch (ParseException e) {
				throw new IllegalArgumentException("OrderNo属性值5到18为不许是时间格式");
			}
		}
		throw new IllegalArgumentException("OrderNo属性值长度非法");
	}

	/**
	 * 从Map中移除元素进行校验，检查是否必填，长度是否正确
	 * @param checkMap
	 * @param fieldName
	 * @param must
	 * @param variableLength
	 * @param maxLength
	 */
	private void checkField(Map<String, String> checkMap, String fieldName, boolean must, boolean variableLength, int maxLength) {
		String field = checkMap.remove(fieldName);
		if (StringUtils.isBlank(field)) {
			if (must) {
				throw new IllegalArgumentException(fieldName + "属性必填，缺没有值");
			} else{
				return;
			}
		}
		// -1表示无长度限制
		if (maxLength == -1) {
			return;
		}
		if (field.length() > maxLength) {
			throw new IllegalArgumentException(fieldName + "长度超过限制");
		}
		if (!variableLength && field.length() != maxLength) {
			throw new IllegalArgumentException(fieldName + "长度不正确");
		}
	}

	/**
	 * 将http请求参数转换成Map<String, String>
	 * @param map
	 * @return
	 */
	private Map<String, String> comvertMap(Map<String, String[]> map) {
		Map<String, String> newMap = new HashMap<String, String>();
		Iterator<Entry<String, String[]>> it = map.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, String[]> entry = it.next();
			String key = entry.getKey();
			String[] value = entry.getValue();
			newMap.put(key, value[0]);
		}
		return newMap;
	}
}

